﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {

    /// <summary>
    /// グラフィック処理の基本となるクラス
    /// </summary>
    /// <remarks>
    /// シングルトン化されていて常に同じインスタンスが帰ります。
    /// </remarks>
    public class Graphics3D {
        static Graphics3D g3d = new Graphics3D ();

        #region Field
        World world;
        FrameBuffer frameBuffer;
        bool colorClearEnabled;
        ClearColor clearColor;
        bool depthClearEnabled;
        float clearDepth;
        Func<World, IEnumerable<Node>> nodeSelection;
        Viewport viewport;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 初期値を変えたらReset()の方も修正するのを忘れずに。
        /// </remarks>
        public Graphics3D () {
            this.frameBuffer = null;
            this.colorClearEnabled = true;
            this.clearColor = new ClearColor (0,0,1, 1);
            this.depthClearEnabled = true;
            this.clearDepth = 1.0f;
            this.nodeSelection = null;
            this.viewport = new Viewport(0,0,0,0);
            this.RenderPass = null;
        }
        #endregion


        #region Property

        /// <summary>
        /// レンダリング対象のシーン
        /// </summary>
        public World World {
            get { return world; }
        }

        /// <summary>
        /// フレームバッファー
        /// </summary>
        /// <remarks>
        /// 現在セットされているフレームバッファーを返します。
        /// デフォルトのフレームバッファーの場合はnullを返します。
        /// </remarks>
        public FrameBuffer FrameBuffer {
            get { return frameBuffer; }
        }


        /// <summary>
        /// ビューポート
        /// </summary>
        public Viewport Viewport {
            get { return viewport; }
        }

        /// <summary>
        /// ノードセレクション
        /// </summary>
        /// <remarks>
        /// シーンを描画するときにノードをある条件にしたがって選択、ソートする事が可能です。
        /// これをノードセレクションといいます。
        /// ノードセレクションの実態は引数<see cref="World"/>を受け取り<see cref="Node"/>の列挙子を返すデリゲートです。
        /// LINQ構文によるラムダ式を想定しています。
        /// </remarks>
        public Func<World, IEnumerable<Node>> NodeSelection {
            get { return nodeSelection; }
        }



        /// <summary>
        /// カラーバッファーのクリアフラグ
        /// </summary>
        /// <remarks>
        /// このプロパティがtrueの時は値<see cref="ClearColor"/>でカラーバッファーをクリアします。
        /// 複数のレンダーターゲットがセットされている場合は、すべてクリアします。
        /// falseの時は何もしません。
        /// </remarks>
        public bool IsColorClearEnabled {
            get { return colorClearEnabled; }
        }

        /// <summary>
        /// デプスバッファーのクリアフラグ
        /// </summary>
        /// <remarks>
        /// このプロパティがtrueの時は値<see cref="ClearDepth"/>でデプスバッファーをクリアします。
        /// falseの時は何もしません。
        /// </remarks>
        public bool IsDepthClearEnabled {
            get { return depthClearEnabled; }
        }

        /// <summary>
        /// クリア・カラー
        /// </summary>
        /// <remarks>
        /// カラーバッファーのクリアに使われる値です。
        /// </remarks>
        public ClearColor ClearColor {
            get { return clearColor; }
        }


        /// <summary>
        /// クリア・デプス
        /// </summary>
        /// <remarks>
        /// デプスバッファーのクリアに使われる値です。
        /// </remarks>
        public float ClearDepth {
            get { return clearDepth; }
        }

        #endregion


        #region Events
        /// <summary>
        /// レンダリングパス・イベント
        /// </summary>
        /// <remarks>
        /// このイベントは<see cref="RenderPassHandler"/>型のメソッドを受け取り、
        /// いわゆるレンダリングパスを設定します。
        /// DDDでは固有のレンダリングパスをエンジン内部で実装せず、
        /// すべて後からユーザーがこのイベントを通して注入します。
        /// </remarks>
        public event RenderPassHandler RenderPass;
        #endregion


        #region Method

        /// <summary>
        /// レンダリング対象のシーンのセット
        /// </summary>
        /// <param name="wld"></param>
        public void SetWorld (World wld) {
            this.world = wld;
        }

        /// <summary>
        /// フレームバッファーのセット
        /// </summary>
        /// <remarks>
        /// nullを指定するとデフォルトのフレームバッファーになります。
        /// </remarks>
        /// <param name="fbuf"></param>
        public void SetFrameBuffer (FrameBuffer fbuf) {
            this.frameBuffer = fbuf;
        }


        /// <summary>
        /// リセット
        /// </summary>
        /// <remarks>
        /// パラメーターを初期値に戻して可能な限りグラフィックデバイスを初期化します。
        /// OpenGLリソースの解放も必要。現状手つかず。
        /// </remarks>
        public void Reset () {
            this.frameBuffer = null;
            this.colorClearEnabled = true;
            this.clearColor = new ClearColor (0, 0, 1, 1);
            this.depthClearEnabled = true;
            this.clearDepth = 1.0f;
            this.nodeSelection = null;
            this.viewport = new Viewport (0, 0, 0, 0);
            this.RenderPass = null;
        }


        /// <summary>
        /// レンダリングパス・イベントの取得
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Delegate> GetRenderPass () {
            return RenderPass.GetInvocationList ();
        }

        /// <summary>
        /// レンダリングパス・イベントの全削除
        /// </summary>
        public void ClearRenderPass () {
            this.RenderPass = null;
        }

        /// <summary>
        /// レンダリングパス・イベントの個数の取得
        /// </summary>
        /// <returns></returns>
        public int GetRenderPassCount () {
            return (RenderPass == null) ? 0 : RenderPass.GetInvocationList ().Length;
        }

        /// <summary>
        /// 唯一の<see cref="Graphics3D"/>オブジェクトの取得
        /// </summary>
        /// <returns>
        /// <see cref="Graphics3D"/>はシングルトンクラスで唯一のインスタンスをこのメソッドを通して取得します。
        /// </returns>
        public static Graphics3D GetInstance () {
            return g3d;
        }

        /// <summary>
        /// ビューポートのセット
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void SetViewort (int x, int y, int width, int height) {
            if (x < 0) {
                throw new ArgumentException ("X is invalid");
            }
            if (y < 0) {
                throw new ArgumentException ("Y is invalid");
            }
            if (width < 0) {
                throw new ArgumentException ("Width is invalid");
            }
            if (height < 0) {
                throw new ArgumentException ("Height is invalid");
            }

            this.viewport = new Viewport(x,y,width, height);
        }

        /// <summary>
        /// カラーのクリア値のセット
        /// </summary>
        /// <remarks>
        /// すべてのチャンネルで有効な値は[0,1]です。
        /// (エラーではなくクランプするべき？)
        /// </remarks>
        /// <param name="r">R(赤)</param>
        /// <param name="g">G(緑)</param>
        /// <param name="b">B(青)</param>
        /// <param name="a">A(不透明度)</param>
        public void SetClearColor (float r, float g, float b, float a) {
            if (r < 0 || r> 1) {
                throw new ArgumentException ("R is invalid");
            }
            if (g < 0 || g > 1) {
                throw new ArgumentException ("G is invalid");
            }
            if (b < 0 || b > 1) {
                throw new ArgumentException ("B is invalid");
            }
            if (a < 0 || a > 1) {
                throw new ArgumentException ("A is invalid");
            }

            this.clearColor = new ClearColor (r, g, b, a);
        }

        /// <summary>
        /// デプスのクリア値のセット
        /// </summary>
        /// <remarks>
        /// 有効な値は[0,1]で1.0fが一番奥です。
        /// (エラーではなくクランプするべき？)
        /// </remarks>
        /// <param name="depth">深さ</param>
        public void SetClearDepth (float depth) {
            if (depth < 0 || depth > 1) {
                throw new ArgumentException ("Depth is invalid");
            }
            this.clearDepth = depth;
        }

        /// <summary>
        /// カラーバッファーのクリアの有効無効を変更します
        /// </summary>
        /// <param name="enable"></param>
        public void SetColorClearEnabled (bool enable) {
            this.colorClearEnabled = enable;
        }

        /// <summary>
        /// デプスバッファーのクリアの有効無効を変更します
        /// </summary>
        /// <param name="enable"></param>
        public void SetDepthClearEnabled (bool enable) {
            this.depthClearEnabled = enable;
        }

        /// <summary>
        /// ノードセレクションを変更します
        /// </summary>
        /// <remarks>
        /// 以前のセレクションを破棄し新しいセレクションに変更します。
        /// </remarks>
        /// <param name="select"></param>
        public void SetNodeSelection (Func<World, IEnumerable<Node>> select) {
            this.nodeSelection = select;
        }

        #endregion



        
    }
}
